package com.pj.lib.auraServer.app;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import com.pj.lib.aura.connection.AuraTypeModel;
import com.pj.lib.aura.connection.LocationModel;
import com.pj.lib.auraServer.app.listeners.OnAuraEvent;
import com.pj.lib.auraServer.app.listeners.OnAuraTypesChange;
import com.pj.lib.auraServer.app.listeners.OnAuraTypesChanged;
import com.pj.lib.auraServer.app.listeners.OnLocationUpdate;
import com.pj.lib.auraServer.app.listeners.OnLocationUpdated;
import com.pj.lib.auraServer.app.listeners.OnUserChange;
import com.pj.lib.auraServer.app.listeners.OnUserChanged;
import com.pj.lib.auraServer.app.properties.AuraProperties;
import com.pj.lib.auraServer.app.properties.DBProperties;
import com.pj.lib.auraServer.app.properties.Properties;
import com.pj.lib.auraServer.app.properties.ServerProperties;
import com.pj.lib.auraServer.components.DB.DB;
import com.pj.lib.auraServer.components.DB.DBComponent;
import com.pj.lib.auraServer.components.configuration.AuraTypesComponent;
import com.pj.lib.auraServer.components.connection.AuraNetworkManager;
import com.pj.lib.auraServer.components.connection.ClientThread;
import com.pj.lib.auraServer.components.connection.NetworkManager;
import com.pj.lib.auraServer.components.lbs.LBSComponent;
import com.pj.lib.auraServer.components.lbs.LocationsPropagationComponent;
import com.pj.lib.auraServer.components.userManagement.AuraEventsComponent;
import com.pj.lib.auraServer.components.userManagement.UserAuraConnectorComponent;
import com.pj.lib.auraServer.components.userManagement.UserManagementComponent;
import com.pj.lib.auraServer.exceptions.AuraServerException;
import com.pj.lib.auraServer.utils.Log;

public class Server {
	
	
	
	
	private AuraNetworkManager mNetworkManager;
	private DBComponent mDBComponent;
	private LBSComponent mLBSComponent;
	private LocationsPropagationComponent mLocationPropagationComponent;
	private AuraEventsComponent mAuraEventsComponent;
	private AuraTypesComponent mAuraTypesComponent;
	private UserAuraConnectorComponent mUserAuraConnectorComponent;
	private UserManagementComponent mUserManagementComponent;
	
	private AuraProperties mAuraProperties;
	private ServerProperties mServerProperties;
	private DBProperties mDBProperties;
	
	
	private List<OnLocationUpdate> mOnLocationUpdateListeners = new ArrayList<OnLocationUpdate>();
	private List<OnLocationUpdated> mOnLocationUpdatedListeners = new ArrayList<OnLocationUpdated>();
	private List<OnUserChange> mOnUserChangeListeners = new ArrayList<OnUserChange>();
	private List<OnUserChanged> mOnUserChangedListeners = new ArrayList<OnUserChanged>();
	private List<OnAuraTypesChange> mOnAuraTypeChangeListeners = new ArrayList<OnAuraTypesChange>();
	private List<OnAuraTypesChanged> mOnAuraTypeChangedListeners = new ArrayList<OnAuraTypesChanged>();
	private List<OnAuraEvent> mOnAuraEventListeners = new ArrayList<OnAuraEvent>();
	
	public Server(HashMap<String, Object> props) {
		mAuraProperties = new AuraProperties();
		mAuraProperties.getSettingsFromMap(props);
		mServerProperties = new ServerProperties();
		mServerProperties.getSettingsFromMap(props);
		mDBProperties = new DBProperties();
		mDBProperties.getSettingsFromMap(props);
		
		setDebugStream( System.out );
		setErrorStream(System.err);
		debug("Starting system...");
		try {
			debug("Initializing components...");
			initializeComponents();
			debug("Components succesfully created.");
		} catch (ClassNotFoundException e) {
			Log.s(this, Log.getStackTrace(e));
			error("- components not succesfully created. Check logs for more info.");
		} catch (SQLException e) {
			Log.s(this, Log.getStackTrace(e));
			error("- components not succesfully created. Check logs for more info.");
		} catch (AuraServerException e) {
			Log.s(this, Log.getStackTrace(e));
			error("- components not succesfully created. Check logs for more info.");
		} catch (Exception e) {
			Log.s(this, Log.getStackTrace(e));
			error("- components not succesfully created. Check logs for more info.");
		}
	}
	
	public Server(String serverPropsXML, String dbPropsXML, String auraPropsXML) {
	//TODO to	
	}
	
	private void initializeComponents() throws ClassNotFoundException, SQLException, AuraServerException, IOException {
		if(mServerProperties.PORT != 0L) {
			debug("1. Initializing Network Manager...");
			mNetworkManager = new AuraNetworkManager(this);
			debug("+ Network Manager created.");
			debug("2. Initializing Database...");
			mDBComponent = new DBComponent(this);
			debug("+ Database created.");
			debug("3. Initializing LBS...");
			mLBSComponent = new LBSComponent(this);
			debug("+ LBS created.");
			debug("4. Initializing Location Propagation...");
			mLocationPropagationComponent = new LocationsPropagationComponent(this);
			debug("+ Location Propagation created...");
			debug("5. Initializing Aura Events...");
			mAuraEventsComponent = new AuraEventsComponent(this);
			debug("+ Aura Events created.");
			debug("6. Initializing Aura Types...");
			mAuraTypesComponent = new AuraTypesComponent(this);
			debug("+ AuraTypes created.");
			debug("7. Initializing User Aura Connector...");
			mUserAuraConnectorComponent = new UserAuraConnectorComponent(this);
			debug("+ User Aura Connector created.");
			debug("8. Initializing User Management...");
			mUserManagementComponent = new UserManagementComponent(this);
			debug("+ User Management created.");
		}
	}
	
	public ServerComponent getComponent(int componentID) {
		switch(componentID) {
		case ServerComponent.COMPONENT_DB:
			return mDBComponent;
		case ServerComponent.COMPONENT_NETWORK_MANAGER:
			return mNetworkManager;
		case ServerComponent.COMPONENT_LBS:
			return mLBSComponent;
		case ServerComponent.COMPONENT_LOCATION_PROPAGATION:
			return mLocationPropagationComponent;
		case ServerComponent.COMPONENT_AURA_EVENTS:
			return mAuraEventsComponent;
		case ServerComponent.COMPONENT_AURA_TYPE:
			return mAuraTypesComponent;
		case ServerComponent.COMPONENT_USER_AURA_CONNECTION:
			return mUserAuraConnectorComponent;
		case ServerComponent.COMPONENT_USER_MANAGEMENT:
			return mUserManagementComponent;
		}
		return null;
	}
	
	public Properties getProperties(int propertiesID) {
		switch(propertiesID) {
		case Properties.PROP_AURA:
			return mAuraProperties;
		case Properties.PROP_DB:
			return mDBProperties;
		case Properties.PROP_SERVER:
			return mServerProperties;
		}
		return null;
	}
	
	
	public synchronized void removeOnUpdateLocation(OnLocationUpdate listener) {
		mOnLocationUpdateListeners.remove(listener);
	}
	
	/**
	 * Adding OnUpdateLocation listener - it is fired when system gets new location 
	 * @param listener
	 */
	public synchronized void addOnUpdateLocation(OnLocationUpdate listener) {
		mOnLocationUpdateListeners.add(listener);
	}
	
	public synchronized void removeOnLocationUpdated(OnLocationUpdated listener) {
		mOnLocationUpdatedListeners.remove(listener);
	}
	
	/**
	 * Adding OnUpdatedLocation listener - it is fired after location db changed
	 * @param listener
	 */
	public synchronized void addOnLocationUpdated(OnLocationUpdated listener) {
		mOnLocationUpdatedListeners.add(listener);
	}
	
	
	public synchronized void removeOnUserChange(OnUserChange listener) {
		mOnUserChangeListeners.remove(listener);
	}
	
	/**
	 * Adding OnUserChange listener - it is fired when system gets user change 
	 * @param listener
	 */
	public synchronized void addOnUserChange(OnUserChange listener) {
		mOnUserChangeListeners.add(listener);
	}
	
	public synchronized void removeOnUserChanged(OnUserChanged listener) {
		mOnUserChangedListeners.remove(listener);
	}
	
	/**
	 * Adding OnUserChange listener - it is fired when system gets user change 
	 * @param listener
	 */
	public synchronized void addOnUserChanged(OnUserChanged listener) {
		mOnUserChangedListeners.add(listener);
	}
	
	public synchronized void removeOnAuraTypeChange(OnAuraTypesChange listener) {
		mOnAuraTypeChangeListeners.remove(listener);
	}
	
	/**
	 * Adding OnAuraTypeChange listener - it is fired when system gets aura type change 
	 * @param listener
	 */
	public synchronized void addOnAuraTypeChange(OnAuraTypesChange listener) {
		
		mOnAuraTypeChangeListeners.add(listener);
		Server.debug(" wielkosc: " + mOnAuraTypeChangeListeners.size());
	}
	
	public synchronized void removeOnAuraTypeChanged(OnAuraTypesChanged listener) {
		mOnAuraTypeChangedListeners.remove(listener);
	}
	
	/**
	 * Adding OnAuraTypeChange listener - it is fired when system gets aura type change 
	 * @param listener
	 */
	public synchronized void addOnAuraTypeChanged(OnAuraTypesChanged listener) {
		mOnAuraTypeChangedListeners.add(listener);
	}
	
	
	public synchronized void removeOnAuraEvent(OnAuraEvent listener) {
		mOnAuraEventListeners.remove(listener);
	}
	
	/**
	 * Adding OnAuraTypeChange listener - it is fired when system gets aura type change 
	 * @param listener
	 */
	public synchronized void addOnAuraEvent(OnAuraEvent listener) {
		mOnAuraEventListeners.add(listener);
	}
	
	
	/**
	 * Fires {@link OnLocationUpdate#locationReceived(int, double[], float)} event
	 * @param userId
	 * @param loc
	 * @param precision
	 */
	public void fireLocationUpdateEvent(int userId, double[] loc, float precision) {
		Iterator<OnLocationUpdate> listeners = mOnLocationUpdateListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().locationReceived(userId, loc, precision);
        }
	}
	
	/**
	 * Fires {@link OnLocationUpdated#locationUpdated()} event
	 */
	public void fireLocationUpdatedEvent(LocationModel location) {
		Iterator<OnLocationUpdated> listeners = mOnLocationUpdatedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().locationUpdated(location.userId, location);
        }
	}
	
	/**
	 * Fires {@link OnUserChange#userLogin(ClientThread, String, String)} event
	 * @param ct
	 * @param username
	 * @param password
	 */
	public void fireUserLoginEvent(ClientThread ct, String username, String password) {
		Iterator<OnUserChange> listeners = mOnUserChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().userLogin(ct, username, password);
        }
	}
	
	/**
	 * Fires {@link OnUserChanged#userLogged(int)} event
	 * @param userId
	 */
	public void fireUserLoggedEvent(int userId) {
		Iterator<OnUserChanged> listeners = mOnUserChangedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().userLogged(userId);
        }
	}
	/**
	 * Fires {@link OnUserChange#userLogout(int)} event
	 * @param userId
	 */
	public void fireUserLogoutEvent(int userId) {
		Iterator<OnUserChange> listeners = mOnUserChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().userLogout(userId);
        }
	}
	
	/**
	 * Fires {@link OnUserChanged#userLogouted(int)} event
	 * @param userId
	 */
	public void fireUserLogoutedEvent(int userId) {
		Iterator<OnUserChanged> listeners = mOnUserChangedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().userLogouted(userId);
        }
	}
	
	/**
	 * Fires {@link OnUserChange#userCreate(ClientThread, String, String)} event
	 * @param ct
	 * @param username
	 * @param password
	 */
	public void fireUserCreateEvent(ClientThread ct, String username, String password) {
		Iterator<OnUserChange> listeners = mOnUserChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().userCreate(ct, username, password);
        }
	}

	/**
	 * Fires {@link OnUserChanged#userCreated(int)} event
	 * @param userId
	 */
	public void fireUserCreatedEvent(int userId) {
		Iterator<OnUserChanged> listeners = mOnUserChangedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().userCreated(userId);
        }
	}
	/**
	 * Fires {@link OnAuraTypesChange#addAuraTypes(ClientThread, AuraTypeModel[])} event
	 * @param ct
	 * @param auraTypes
	 */
	public void fireAuraTypesAddEvent(ClientThread ct, AuraTypeModel[] auraTypes) {
		Iterator<OnAuraTypesChange> listeners = mOnAuraTypeChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().addAuraTypes(ct, auraTypes);
        }
	}
	
	/**
	 * Fires {@link OnAuraTypesChanged#auraTypesAdded()} event
	 */
	public void fireAuraTypesAddedEvent(AuraTypeModel[] auraTypes) {
		Iterator<OnAuraTypesChanged> listeners = mOnAuraTypeChangedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().auraTypesAdded(auraTypes);
        }
	}
	/**
	 * Fires {@link OnAuraTypesChange#updateAuraType(ClientThread, AuraTypeModel, String)} event
	 * @param ct
	 * @param auraType
	 * @param code
	 */
	public void fireAuraTypeUpdateEvent(ClientThread ct, AuraTypeModel auraType, String code) {
		Iterator<OnAuraTypesChange> listeners = mOnAuraTypeChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().updateAuraType(ct, auraType, code);
        }
	}
	
	/**
	 * Fires {@link OnAuraTypesChanged#auraTypeUpdated()} event
	 */
	public void fireAuraTypeUpdatedEvent(AuraTypeModel type) {
		Iterator<OnAuraTypesChanged> listeners = mOnAuraTypeChangedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().auraTypeUpdated(type);
        }
	}
	/**
	 * Fires {@link OnAuraTypesChange#deleteAuraType(ClientThread, String, String)} event
	 * @param ct
	 * @param code
	 * @param replacementCode
	 */
	public void fireAuraTypeDeleteEvent(ClientThread ct, String code, String replacementCode) {
		Iterator<OnAuraTypesChange> listeners = mOnAuraTypeChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().deleteAuraType(ct, code, replacementCode);
        }
	}
	
	/**
	 * Fires {@link OnAuraTypesChanged#auraTypeDeleted()} event
	 */
	public void fireAuraTypeDeletedEvent(String code) {
		Iterator<OnAuraTypesChanged> listeners = mOnAuraTypeChangedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().auraTypeDeleted(code);
        }
	}
	/**
	 * Fires {@link OnUserChange#deleteAuraType(ClientThread, String, String)} event
	 * @param ct
	 * @param code
	 */
	public void fireAuraTypeGetEvent(ClientThread ct, String code) {
		Server.debug("sending get aura type event. Number of listeners: " 
				+ mOnAuraTypeChangeListeners.size());
		Iterator<OnAuraTypesChange> listeners = mOnAuraTypeChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().getAuraType(ct, code);
        }
	}
	
	/**
	 * Fires {@link OnUserChange#setAura(ClientThread, String, int)} event
	 * @param ct
	 * @param code
	 * @param userId
	 */
	public void fireUserAuraSetEvent(ClientThread ct, String code, int userId) {
		Iterator<OnUserChange> listeners = mOnUserChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().setAura(ct, code, userId);
        }
	}
	
	/**
	 * Fires {@link OnUserChanged#auraSet(int)} event
	 * @param userId
	 */
	public void fireAfterUserAuraSetEvent(int userId, String auraCode) {
		Iterator<OnUserChanged> listeners = mOnUserChangedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().auraSet(userId, auraCode);
        }
	}
	
	/**
	 * Fires {@link OnUserChange#disableAura(ClientThread, int)} event
	 * @param ct
	 * @param userId
	 */
	public void fireUserAuraDisableEvent(ClientThread ct, int userId) {
		Iterator<OnUserChange> listeners = mOnUserChangeListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().disableAura(ct, userId);
        }
	}
	
	/**
	 * Fires {@link OnUserChanged#auraDisabled(int)} event
	 * @param userId
	 */
	public void fireUserAuraDisabledEvent(int userId) {
		Iterator<OnUserChanged> listeners = mOnUserChangedListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().auraDisabled(userId);
        }
	}
	
	/**
	 * Fires {@link OnAuraEvent#enteredAura(int, int, String)} event
	 * @param userId
	 */
	public void fireEnteredAuraEvent(int acId, int asuId, String action) {
		Iterator<OnAuraEvent> listeners = mOnAuraEventListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().enteredAura(acId, asuId, action);
        }
	}
	
	/**
	 * Fires {@link OnAuraEvent#leftAura(int, int, String)} event
	 * @param userId
	 */
	public void fireLeftAuraEvent(int acId, int asuId, String action) {
		Iterator<OnAuraEvent> listeners = mOnAuraEventListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().leftAura(acId, asuId, action);
        }
	}
	
	/**
	 * Fires {@link OnAuraEvent#temporaryLeftAura(int, int, String)} event
	 * @param userId
	 */
	public void fireTemporaryLeftAuraEvent(int acId, int asuId, String action) {
		Iterator<OnAuraEvent> listeners = mOnAuraEventListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().temporaryLeftAura(acId, asuId, action);
        }
	}
	
	/**
	 * Fires {@link OnAuraEvent#temporaryLeftAura(int, int, String)} event
	 * @param userId
	 */
	public void fireReturnFromTemporaryLeftAuraAuraEvent(int acId, int asuId, String action) {
		Iterator<OnAuraEvent> listeners = mOnAuraEventListeners.iterator();
        while( listeners.hasNext() ) {
            listeners.next().returnFromTemporaryLeftAura(acId, asuId, action);
        }
	}
	
	// everything below provides a simple debug system for
	// this package
	// set this to a print stream if you want debug info
	// sent to it; otherwise, leave it null
	static private PrintStream debugStream;
	static private PrintStream errorStream;
	// we have two versions of this ...
	static public void setDebugStream( PrintStream ps ) {
		debugStream = ps;
	}
	// ... just for convenience
	static public void setDebugStream( OutputStream out ) {
		debugStream = new PrintStream( out );
	}
	// we have two versions of this ...
	static public void setErrorStream( PrintStream ps ) {
		errorStream = ps;
	}
	// ... just for convenience
	static public void setErrorStream( OutputStream out ) {
		errorStream = new PrintStream( out );
	}
	// send debug info to the print stream, if there is one
	static public void debug( String s ) {
		if (debugStream != null)
			debugStream.println( s );
		
	}
	static public void error( String s) {
		if (errorStream != null)
			errorStream.println( s );
	}
}
